Unity[Tips]异步编程
C#中Async、await、Task
async
修饰符可将方法、lambda表达式或匿名方法指定为异步。如果对方法或表达式使用此修饰符,则其称为异步方法。这是告诉编译器我们希望能够在其中执行代码,并允许该方法的调用方在等待该方法完成时继续执行的一种方式。
await
运算符暂停对其所属的 async 方法的求值,直到其操作数表示的异步操作完成。 异步操作完成后,await 运算符将返回操作的结果(如果有)。
task
类 Task 表示不返回值且通常异步执行的单个操作。 由于对象执行 Task 的工作通常在线程池线程上异步执行,而不是在主应用程序线程上同步执行。
Unity中异步编程
1.async、await
Unity中引用
System.Threading.Tasks
,Unity中并不能完美支持多线程,部分UnityAPI不能在其中运行,过多的异步task会导致性能问题,而且WebGL仅支持单线程,使用需要特别注意。async void 和async task,这里的主要区别是,其他异步方法无法等待定义为“async void”的方法。这表明我们应该总是更喜欢用返回类型Task定义我们的异步方法,以便我们可以“等待”它们。
此规则的唯一例外是当从非异步代码调用异步方法时
1 |
|
在此示例中,当用户单击按钮”Start Task”时,我们要启动异步方法。如果在RunTaskAsync方法中发生任何异常,它们将以静默方式发生。该异常将不会记录到Unity控制台。这是因为当异步方法返回Task时发生异常时,它们将被返回的Task对象捕获,而不是被Unity抛出和处理。此行为存在的原因很充分:允许异步代码与try-catch块一起正常工作。以下面的代码为例:
1 |
|
异常由DoSomethingElseAsync方法返回的Task捕获,并且仅在await时才重新抛出。如您所见,调用异步方法与等待它们不同,这就是为什么必须让Task对象捕获异常。因此,在上面的OnGUI示例中,当在RunTaskAsync方法中抛出异常时,它会被返回的Task对象捕获,并且由于此Task上没有任何内容,因此异常不会冒泡到Unity。
想要从非异步代码调用异步方法的问题。在上面的示例中,我们希望从OnGUI方法内部启动RunTaskAsync异步方法,我们不关心等待它完成,因此我们不希望只添加await以便可以记录异常。
永远不要在没有等待返回的Task的情况下调用async Task方法。如果不想等待异步行为完成,则应该调用async void方法。
所以我们的例子变成:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 public class AsyncExample : MonoBehaviour
{
public void OnGUI()
{
if (GUI.Button(new Rect(100, 100, 100, 100), "Start Task"))
{
RunTask();
}
}
async void RunTask()
{
await RunTaskAsync();
}
async Task RunTaskAsync()
{
Debug.Log("Started task...");
await new WaitForSeconds(1.0f);
throw new Exception();
}
}
如果再次运行此代码,您现在应该看到记录了异常。这是因为当在RunTask方法中的await期间抛出异常时,它会冒泡到Unity并记录到控制台,因为在这种情况下没有任何Task对象可以捕获它。
作为创建自己的“async void”方法的替代方法,您还可以使用辅助方法来执行等待:
1 |
|
WrapErrors()方法只是确保等待任务的通用方法,因此Unity将始终接收任何抛出的异常。它只是做了等待,就是这样:
1 |
|
当需要忽略异步方法task返回值,可以使用_
消除警告。但是这样同时也会忽略 CountDownAsync() 中的异常。
1 |
|
其他注意事项:
https://zhuanlan.zhihu.com/p/596255416
参考
https://blog.csdn.net/chinaherolts2008/article/details/115021064
https://blog.csdn.net/avi9111/article/details/121753627
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!